<?php

declare(strict_types=1);

namespace Vipkwd\SDK\Qrcdr\Dependents;

use Vipkwd\SDK\Qrcdr\Dependents\Http;
use Vipkwd\SDK\Qrcdr\Dependents\Data;
use \Exception;

trait Qrcdr_Core
{
    private $options = [];
    private $except = null;

    private $_api = 'http://qrcode.hosts.run/v1/api/generator';

    private static $sdk;

    public function __get(string $property)
    {
        switch ($property) {
            case "host":
                return $this->options['scheme'] ? ($this->options['scheme'] . '://' . $this->options['host']) : null;
            case "appId":
                return $this->options['appId'] ?? null;
            case "appKey":
                return $this->options['appKey'] ?? null;
            case "version":
                $p = explode('/', $this->options['path']);
                $p[] = '';
                return $p[1];
            case "api":
                return $this->options['api'];
            case "except":
            case "exception":
                return $this->except;
            default:
                return null;
        }
    }

    private function exceptionHandler($response, callable $callback): array
    {
        if (!is_array($response)) {
            $response = [
                "error" => "server error!",
                "error_description" => is_bool($response) ? $response : $response,
            ];
        }
        if (isset($response['error']) && isset($response['error_description'])) {
            $this->except = $response;
            throw new Exception($response['error_description']);
        }
        $callback($response);
        return $response;
    }

    private function _getOptionValue($value, $optionKey = null)
    {
        return $value ?? ($optionKey ? ($this->options[$optionKey] ?? '') : '');
    }

    /**
     * URL关联填充
     */
    private function padRelationUrl(string $url, array $params = []): string
    {
        $url = trim(urldecode($url));
        if (substr($url, 0, 4) != 'http') {
            if (substr($url, 0, 2) == '//') {
                $url = $_SERVER['REQUEST_SCHEME'] . ':' . $url;
            } else {
                $url = $_SERVER['REQUEST_SCHEME'] . '://' . str_replace([':80', ':443'], '', $_SERVER['HTTP_HOST']) . '/' . ltrim($url, '/');
            }
        }
        return $url . "?" . http_build_query($params);
    }

    private function send(string $type, array $params, array $data)
    {
        $data = array_merge($this->__commonParams($params, $type), $data);
        $data['appId'] = $this->options['appId'];
        $data['time'] = time();
        $data['sign'] = $this->signBuilder($data);
        return $this->decrypt(
            Http::post(
                $this->api,
                $data
            )
        );
    }

    private function signBuilder(array $data)
    {
        if (isset($data['sign']))
            unset($data['sign']);
        // $data['appId'] = $this->options['appId'];
        ksort($data);
        $str = self::cryptRC4(md5(http_build_query($data)), $this->options['appId']);
        return md5($str);
    }

    private function decrypt($data)
    {
        if (isset($data['errore'])) {
            return $data;
        }
        if (is_array($data) && isset($data['sign']) && isset($data['data'])) {
            $sign = $data['sign'];
            $data = self::cryptRC4($data['data'], $this->options['appKey'], 'D');
            if ($sign === $this->signBuilder(is_array($data) ? $data : [])) {
                return $data;
            }
        }
        return [];
    }

    private static function cryptRC4(string $str, string $key = '', string $operation = 'E')
    {
        $operation = strtoupper($operation);
        $key = md5($key);
        $key_length = strlen($key);
        if ($operation == 'D') {
            $str = str_replace(["%2F", "%2B"], ["/", "+"], $str);
            $str = base64_decode($str);
        } else {
            $str = substr(md5($str . $key), 0, 8) . $str;
        }
        $str_length = strlen($str);
        $rndkey = $box = array();
        $rest = '';
        for ($i = 0; $i <= 255; $i++) {
            $rndkey[$i] = ord($key[$i % $key_length]);
            $box[$i] = $i;
        }
        for ($j = $i = 0; $i < 256; $i++) {
            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }
        for ($a = $j = $i = 0; $i < $str_length; $i++) {
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $rest .= chr(ord($str[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
            // dump($str[$i] . ' -> ' . ord($str[$i]) . " -> ");
        }
        if ($operation == 'D') {
            if (substr($rest, 0, 8) == substr(md5(substr($rest, 8) . $key), 0, 8)) {
                $rest = substr($rest, 8);
                try {
                    $data = json_decode($rest, true);
                } catch (Exception $e) {
                    $data = $rest;
                }
                return $data ? $data : $rest;
            } else {
                return '';
            }
        } else {
            $rest = str_replace('=', '', base64_encode($rest));
            return urlencode($rest);
        }
    }

    private function __commonParams(array $params, string $type)
    {
        $data = Data::instance($params);
        return [
            'section' => "#{$type}",
            'level' => $data->filterIn(explode('|', 'L|M|Q|H'))->get('level', 'M'),
            'style' => $data->filterIn(explode('|', '3d|plus|default|circle'))->get('style', 'default'),
            'size' => min(
                max(
                    $data->typeInt()->get('size', 4),
                    4
                ),
                32
            ),
        ];
    }
}
